package emulador;
import java.util.LinkedList;

public class SAM {
        // Codigos de operacion
        public static final int INST_LOAD = 0x0;
        public static final int INST_LOADA = 0x1;
        public static final int INST_LOADI = 0x2;
        public static final int INST_LOADL = 0x3;
        public static final int INST_STORE = 0x4;
        public static final int INST_STOREI = 0x5;
        public static final int INST_CALL = 0x6;
        public static final int INST_CALLI = 0x7;
        public static final int INST_RETURN = 0x8;
        public static final int INST_PUSH = 0x9;
        public static final int INST_POP = 0xA;
        public static final int INST_JUMP = 0xB;
        public static final int INST_JUMPI = 0xC;
        public static final int INST_JUMPIF = 0xD;
        public static final int INST_HALT = 0xE;
        public static final int INST_ILLEGAL = 0xF;

        // Codigos de las primitivas
        public static final int PRIM_ID = 0x00;
        public static final int PRIM_NOT = 0x01;
        public static final int PRIM_AND = 0x02;
        public static final int PRIM_OR = 0x03;
        public static final int PRIM_INC = 0x04;
        public static final int PRIM_DEC = 0x05;
        public static final int PRIM_NEG = 0x06;
        public static final int PRIM_ADD = 0x07;
        public static final int PRIM_SUB = 0x08;
        public static final int PRIM_MULT = 0x09;
        public static final int PRIM_DIV = 0x0A;
        public static final int PRIM_MOD = 0x0B;
        public static final int PRIM_LT = 0x0C;
        public static final int PRIM_LE = 0x0D;
        public static final int PRIM_GT = 0x0E;
        public static final int PRIM_GE = 0x0F;
        public static final int PRIM_EQ = 0x10;
        public static final int PRIM_NE = 0x11;
        public static final int PRIM_TO21 = 0x12;
        public static final int PRIM_TO22 = 0x13;
        public static final int PRIM_NEW = 0x14;
        public static final int PRIM_FREE = 0x15;
        public static final int PRIM_GET = 0x16;
        public static final int PRIM_PUT = 0x17;

        // Codigos de los punteros
        public static final int REGISTER_CB = 0x0;
        public static final int REGISTER_CT = 0x1;
        public static final int REGISTER_PB = 0x2;
        public static final int REGISTER_PT = 0x3;
        public static final int REGISTER_SB = 0x4;
        public static final int REGISTER_ST = 0x5;
        public static final int REGISTER_HB = 0x6;
        public static final int REGISTER_HT = 0x7;
        public static final int REGISTER_LB = 0x8;
        public static final int REGISTER_L1 = 0x9;
        public static final int REGISTER_L2 = 0xA;
        public static final int REGISTER_L3 = 0xB;
        public static final int REGISTER_L4 = 0xC;
        public static final int REGISTER_L5 = 0xD;
        public static final int REGISTER_L6 = 0xE;
        public static final int REGISTER_CP = 0xF;
        
        // Registros
        private int CB;
        private int CT;
        private int SB;
        private int ST;
        private int LB;
        private int HB;
        private int HT;

        private LinkedList<Instruccion> code;
        private LinkedList<Word> heap;
        private LinkedList<Word> stack;

        private int programCounter;

        public SAM(LinkedList<Instruccion> code) {
                this.code = code;
                
                heap = new LinkedList<Word>();
                stack = new LinkedList<Word>();
                
                programCounter = 0;
                
                CB = 0;
                CT = code.size();
                
                SB = 0;
                ST = stack.size();
                
                LB = 0;
                
                HB = 0;
                HT = heap.size();
        }

        public void reset() {
                heap = new LinkedList<Word>();
                stack = new LinkedList<Word>();
                
                programCounter = 0;
                
                CB = 0;
                CT = code.size();
                
                SB = 0;
                ST = stack.size();
                
                LB = 0;
                
                HB = 0;
                HT = heap.size();
        }

        public void run() {
                Instruccion inst = runStep();

                while (inst.getOpcode() != INST_HALT)
                        inst = runStep();
        }

        public Instruccion runStep() {
                Instruccion inst = code.get(programCounter);
                programCounter++;
                
                printStack();
                System.out.println("---> Ejecutando instruccion: " + inst);

                switch (inst.getOpcode()) {
                case INST_LOAD:
                        LOAD(inst);
                        break;
                case INST_LOADA:
                        LOADA(inst);
                        break;
                case INST_LOADI:
                        LOADI(inst);
                        break;
                case INST_LOADL:
                        LOADL(inst);
                        break;
                case INST_STORE:
                        STORE(inst);
                        break;
                case INST_STOREI:
                        STOREI(inst);
                        break;
                case INST_CALL:
                        switch (inst.getRegister()) {
                        case REGISTER_PB:
                                switch (inst.getOperand()) {
                                case PRIM_ID:
                                        break;
                                case PRIM_NOT:
                                        break;
                                case PRIM_AND:
                                        break;
                                case PRIM_OR:
                                        break;
                                case PRIM_INC:
                                        INC(inst);
                                        break;
                                case PRIM_DEC:
                                        DEC(inst);
                                        break;
                                case PRIM_NEG:
                                        break;
                                case PRIM_ADD:
                                        ADD(inst);
                                        break;
                                case PRIM_SUB:
                                        SUB(inst);
                                        break;
                                case PRIM_MULT:
                                        MULT(inst);
                                        break;
                                case PRIM_DIV:
                                        break;
                                case PRIM_MOD:
                                        break;
                                case PRIM_LT:
                                        break;
                                case PRIM_LE:
                                        break;
                                case PRIM_GT:
                                        break;
                                case PRIM_GE:
                                        break;
                                case PRIM_EQ:
                                        break;
                                case PRIM_NE:
                                        break;
                                case PRIM_TO21:
                                        break;
                                case PRIM_TO22:
                                        break;
                                case PRIM_NEW:
                                        break;
                                case PRIM_FREE:
                                        break;
                                case PRIM_GET:
                                        GET(inst);
                                        break;
                                case PRIM_PUT:
                                        PUT(inst);
                                        break;
                                }
                                break;
                        default:
                                CALL(inst);
                                break;
                        }

                        break;
                case INST_CALLI:
                        CALLI(inst);
                        break;
                case INST_RETURN:
                        RETURN(inst);
                        break;
                case INST_PUSH:
                        PUSH(inst);
                        break;
                case INST_POP:
                        POP(inst);
                        break;
                case INST_HALT:
                        HALT(inst);
                        break;
                }

                return inst;
        }

        // DONE
        private void LOAD (Instruccion inst) {
                System.out.println("Cargando " + inst.getSize() + " palabras desde " + inst.getOperand() + "[" + inst.getRegister() + "].");
                
                for (int i = 0; i < inst.getSize(); i++) {
                        switch (inst.getRegister()) {
                        case REGISTER_SB:
                                stack.addLast(new Word(stack.get(inst.getOperand() + i).getWord()));
                                break;
                        case REGISTER_LB:
                                stack.addLast(new Word(stack.get(LB + inst.getOperand() + i).getWord()));
                                break;
                        }
                }
                
                ST = stack.size();
        }
        
        // DONE
        private void LOADA (Instruccion inst) {
                System.out.println("Empujando direccion " + inst.getOperand() + "[" + inst.getRegister() + "] en la pila.");
                
                switch (inst.getRegister()) {
                case REGISTER_SB:
                        stack.addLast(new Word(SB + inst.getOperand()));
                        break;
                case REGISTER_LB:
                        stack.addLast(new Word(LB + inst.getOperand()));
                        break;
                }
                
                ST = stack.size();
        }
        
        // DONE
        private void LOADI (Instruccion inst) {
                LinkedList<Word> tempStack = new LinkedList<Word>();
                
                System.out.println("Sacando direccion de la pila.");
                
                Word w = stack.removeLast();
                
                System.out.println("Copiando " + inst.getSize() + " palabras a partir de " + w.getWord() + ".");
                
                for(int i = 0; i < inst.getSize(); i++)
                        tempStack.add(stack.get(w.getWord() + i));
                
                System.out.println("Empujando " + inst.getSize() + " palabras en la pila.");
                
                for(Word word : tempStack)
                        stack.addLast(word);
                                
                ST = stack.size();
        }

        // DONE
        private void LOADL (Instruccion inst) {
                System.out.println("Empujando en la pila el literal " + inst.getOperand() + ".");
                
                stack.addLast(new Word(inst.getOperand()));
                
                ST = stack.size();
        }
        
        // DONE
        private void STORE (Instruccion inst) {
                LinkedList<Word> tempStack = new LinkedList<Word>();
                
                System.out.println("Sacando " + inst.getSize() + " palabras de la pila.");
                
                for (int i = 0; i < inst.getSize(); i++)
                        tempStack.addFirst(stack.removeLast());
                
                System.out.println("Empujando " + inst.getSize() + " palabras en " + inst.getOperand() + "[" + inst.getRegister() + "].");
                
                for (int i = 0; i < tempStack.size(); i++) {
                        switch (inst.getRegister()) {
                        case REGISTER_SB:
                                stack.get(inst.getOperand() + i).setWord(tempStack.get(i).getWord());
                                break;
                        case REGISTER_LB:
                                stack.get(inst.getOperand() + i + LB).setWord(tempStack.get(i).getWord());
                                break;
                        }
                }
                
                ST = stack.size();
        }
        
        // DONE
        private void STOREI (Instruccion inst) {
                LinkedList<Word> tempStack = new LinkedList<Word>();
                
                System.out.println("Sacando " + inst.getSize() + " palabras de la pila.");
                
                for (int i = 0; i < inst.getSize(); i++)
                        tempStack.addFirst(stack.removeLast());
                
                System.out.println("Sacando direccion de la pila.");
                
                Word w = stack.removeLast();
                
                System.out.println("Empujando " + inst.getSize() + " palabras en la pila.");
                
                for (int i = 0; i < tempStack.size(); i++) {
                        switch (inst.getRegister()) {
                        case REGISTER_SB:
                                stack.get(w.getWord() + i).setWord(tempStack.get(i).getWord());
                                break;
                        case REGISTER_LB:
                                stack.get(w.getWord() + i + LB).setWord(tempStack.get(i).getWord());
                                break;
                        }
                }
        }
        
        // DONE
        private void CALL (Instruccion inst) {
                System.out.println("Llamando a subrutina en " + inst.getOperand() + ".");
                
                int LBTemp = stack.size();
                
                stack.addLast(new Word(programCounter));
                stack.addLast(new Word(LB));

                LB = LBTemp;
                programCounter = inst.getOperand();
                
                ST = stack.size();
        }
        
        // WIP
        private void CALLI (Instruccion inst) {
                
        }
        
        // DONE
        private void RETURN (Instruccion inst) {
                LinkedList<Word> tempStack = new LinkedList<Word>();
                
                System.out.println("Sacando " + inst.getSize() + " palabras de la pila.");
                
                for (int i = 0; i < inst.getSize(); i++)
                        tempStack.addFirst(stack.removeLast());
                
                // Sacamos de la pila palabras innecesarias
                for (int i = stack.size(); i > LB + 2; i--)
                        stack.removeLast();
                
                System.out.println("Sacando registro de activacion del tope de la pila.");
                
                LB = stack.removeLast().getWord();
                programCounter = stack.removeLast().getWord();
                
                System.out.println("Sacando " + inst.getOperand() + " palabras de la pila.");
                
                for (int i = 0; i < inst.getOperand(); i++)
                        stack.removeLast();
                
                System.out.println("Empujando " + inst.getSize() + " palabras en la pila.");
                
                for (Word w : tempStack)
                        stack.addLast(w);
                
                ST = stack.size() + stack.size();
        }
        
        // DONE
        private void PUSH (Instruccion inst) {
                System.out.println("Empujando " + inst.getOperand() + " palabras en la pila.");
                
                for (int i = 0; i < inst.getOperand(); i++)
                        stack.addLast(new Word(0));
                
                ST = stack.size();
        }
        
        // DONE
        private void POP (Instruccion inst) {
                LinkedList<Word> tempStack = new LinkedList<Word>();
                
                System.out.println("Sacando " + inst.getSize() + " palabras de la pila.");
                
                for (int i = 0; i < inst.getSize(); i++)
                        tempStack.addFirst(stack.removeLast());
                
                System.out.println("Sacando " + inst.getOperand() + " palabras de la pila.");
                
                for (int i = 0; i < inst.getOperand(); i++)
                        stack.removeLast();
                
                System.out.println("Empujando " + inst.getSize() + " palabras en la pila.");
                
                for (Word w : tempStack)
                        stack.addLast(w);
                
                ST = stack.size();
        }
        
        
        
        // DONE
        private void HALT (Instruccion inst) {
                System.out.println("Deteniendo emulacion.");
        }
        
        // WARNING - PRIMITIVES BEYOND THIS POINT
        
        // INC
        private void INC (Instruccion inst) {
                System.out.println("Incrementando operando de " + inst.getSize() + " palabras.");
                Word low;           
                switch (inst.getSize()) {
                case 1:
                        low = stack.removeLast();
                        
                        low.setWord(low.getWord() + 1);

                        stack.addLast(low);
                        break;
     
                }
                
                ST = stack.size();
        }
        
        // DEC
        private void DEC (Instruccion inst) {
                System.out.println("Decrementando operando de " + inst.getSize() + " palabras.");
                Word low;
 
                switch (inst.getSize()) {
                case 1:
                        low = stack.removeLast();
                        
                        low.setWord(low.getWord() - 1);

                        stack.addLast(low);
                        break;
                }
                
                ST = stack.size();
        }
        
        // ADD
        private void ADD (Instruccion inst) {
                System.out.println("Sumando operandos de " + inst.getSize() + " palabras.");
                Word low1;
                Word low2;             
                Word lowResult;
                
                switch (inst.getSize()) {
                case 1:
                        low1 = stack.removeLast();
                        low2 = stack.removeLast();
                        
                        lowResult = new Word(low2.getWord() + low1.getWord());

                        stack.addLast(lowResult);
                        break;
                }
                ST = stack.size();
        }
        
        // SUB
        private void SUB (Instruccion inst) {
                System.out.println("Sumando operandos de " + inst.getSize() + " palabras.");
                Word low1;
                Word low2;
                Word lowResult;
                
                switch (inst.getSize()) {
                case 1:
                        low1 = stack.removeLast();
                        low2 = stack.removeLast();
                        
                        lowResult = new Word(low2.getWord() - low1.getWord());

                        stack.addLast(lowResult);
                        break;
                }
                
                ST = stack.size();
        }
        
        // MULT
        private void MULT (Instruccion inst) {
                System.out.println("Multiplicando operandos de " + inst.getSize() + " palabras.");
                Word low1;
                Word low2;
                Word lowResult;
                
                switch (inst.getSize()) {
                case 1:
                        low1 = stack.removeLast();
                        low2 = stack.removeLast();
                        
                        lowResult = new Word(low2.getWord() * low1.getWord());

                        stack.addLast(lowResult);
                        break;
                }
                
                ST = stack.size();
        }
        
        // GET
        private void GET (Instruccion inst) {
                System.out.println("Leyendo " + inst.getSize() + " palabras.");
                Word low;

                
                switch (inst.getSize()) {
                case 1:
                        low = stack.removeLast();
                        
                        low.setWord(3);
                        
                        stack.addLast(low);
                        break;
                }
                
                ST = stack.size();
        }
        
        // PUT
        private void PUT (Instruccion inst) {
                System.out.println("Imprimiendo " + inst.getSize() + " palabras.");
                Word low;

                switch (inst.getSize()) {
                case 1:
                        low = stack.getLast();
                        
                        System.out.println(low.getWord());
                        break;
                }
                
                ST = stack.size();
        }
        
        public void printStack() {
                System.out.print("SB ");
                
                for(int i = 0; i < stack.size(); i++) {
                        if (i == LB)
                                System.out.print("LB ");
                        
                        System.out.print(stack.get(i).getWord() + " ");
                }
                
                System.out.println("ST");
        }

}